(function( $ ) {
  let methods = {
    // 插件初始化
    init: function(params) {
      return this.each(function() {
        // 形成插件设置（“粘合”内部和外部设置）
        let config = { slideDur: 400 };
        let options = $.extend({}, config, params);

        // 将设置与其所有者相关联 - 相应的 DOM 元素
        let $this = $(this);
        for(let key in options) {
          $this.data(key, options[key]);
        }

        // 绘制搜索字段和用于扩展选项列表的图标
        $( $this.data('element') ).addClass('combotree');
        methods.renderField($this);

        // 挂起事件处理程序
        $(document).bind('mouseup', methods.onWindowMouseUp);

        // 创建将用于搜索列表的自定义 css 选择器
        $.extend($.expr[':'], {
          containsIN: function(elem, i, match) {
            return (elem.innerText || '').toLowerCase().indexOf((match[3] || '').toLowerCase()) >= 0;
          }
        });
      });
    },

    /**
     * 输入域渲染
     * @param {Element} 调用插件的元素的元素 DOM 元素
     */
    renderField: function(element) {
      // 如果设置了字段名，那么对应的元素就会被绘制
      if( element.data('label') ) {
        $('<label class="combotree__label" for="combotree__search">' + element.data('label')  + '</label>').appendTo( $( element.data('element') ) );
      }

      // 输入字段的容器和用于扩展选项列表的按钮
      let inputContainer = $('<div class="combotree__field"></div>').appendTo( $( element.data('element') ) );
      if( !element.data('label') ) {
        inputContainer.addClass('without-label');
      }

      // 输入字段
      let placeholder = element.data('placeholder') || '';
      let input = $('<input id="combotree__input" class="combotree__input" type="search" placeholder="' + placeholder + '">')
          .appendTo(inputContainer)
          .bind('keyup', methods.onInputChange);

      // 打开选项列表的按钮
      $('<button class="combotree__btn" type="button">')
          .appendTo(inputContainer)
          .bind('click', methods.onSelectClick);

      // 选项列表的容器
      let listMaxHeight = element.data('listHeight') || '240';
      let wrapper = $('<div class="combotree__list-wrapper"></div>').appendTo( $( element.data('element') ) );
      wrapper.css('max-height', listMaxHeight + 'px');

      // 设置默认值
      if( element.data('value') ) {
        let defaultValue = methods.findDefaultItem( element.data('data').children, element.data('value') );
        input.val( defaultValue.name );
      }
    },

    /**
     * 呈现下拉列表的内容
     * @param {Element} 调用插件的元素的元素 DOM 元素
     */
    renderList: function(element) {
      methods.renderNodes( element.data('data'), element.find('.combotree__list-wrapper'));
    },

    /**
     * 生成和绘制下拉列表内容的递归函数
     * @param {Array}   childrenDataObj 有关列表选项的信息
     * @param {Element} parentElement   列出变体父 DOM 元素
     */
    renderNodes: function(childrenDataObj, parentElement) {
      if(childrenDataObj) {
        // 列表选项的容器渲染
        let childContainer = $('<ul class="combotree__list"></ul>');
        childContainer.appendTo(parentElement);
        // 列表选项排序
        childrenDataObj.sort(function(a, b) {
          let compA = a.title.toUpperCase();
          let compB = b.title.toUpperCase();
          return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
        });
        // 绘制列表选项
        childrenDataObj.forEach(function(child) {
          parentElement = methods.renderItem(child.title, childContainer, !child.children);
          methods.renderNodes(child.children, parentElement);
        });
      }
    },

    /**
     * 绘制列表项
     * @param title
     * @param  {Element}  parent      列表元素的父元素的 DOM 元素
     * @param  {Boolean}  isLastLevel 指示该元素是列表层次结构中的最低级别
     * @return {Element}  itemElement 列出变体 DOM 元素
     */
    renderItem: function(title, parent, isLastLevel) {
      let lastLevelClass = isLastLevel ? 'combotree__item_last-level' : '';
      let itemElement = $('<li class="combotree__item ' + lastLevelClass + '">' + title + '</li>' );
      itemElement.appendTo(parent);
      return itemElement;
    },

    /**
     * 展开下拉列表
     * @param {Element} 调用插件的元素的 DOM 元素
     */
    showList: function(element) {
      // 计算元素的底部位置
      let listMaxHeight = element.data('listHeight') || '240';
      let elementBottomPos = element.offset().top + element.height() + +listMaxHeight;

      if(elementBottomPos < $(window).height()) {
        // 如果元素可以向下展开（有足够的空间），则向下展开
        element.find('.combotree__list-wrapper').slideDown( element.data('slideDur') ).bind('click', methods.onItemClick);
      } else {
        // 如果没有足够的空间向下扩展元素，则向上扩展。
        element.find('.combotree__list-wrapper').addClass('openToTop').slideDown( element.data('slideDur') ).bind('click', methods.onItemClick);
      }

      // 绘制选项列表
      methods.renderList(element);

      // 如果输入字段已填充，则相应的值会在选项列表中突出显示
      let inputValue = element.find('.combotree__input').val();
      if(inputValue !== '') {
        methods.searchItem(inputValue, element);
      }

      // 打开下拉列表
      element.addClass('open');
    },

    /**
     * 折叠下拉列表
     * @param {Element} 调用插件的元素的元素 DOM 元素
     */
    hideList: function(element) {
      element.find('.combotree__list-wrapper').slideUp( element.data('slideDur') ).unbind('click', methods.onItemClick);
      element.find('.combotree__list-wrapper').children().removeClass('open').removeClass('hidden');

      setTimeout(function() {
        element.removeClass('open');
        element.find('.combotree__list-wrapper').removeClass('openToTop').children().remove();
      }, 550);
    },

    /**
     * 在满足输入条件的选项列表中搜索
     * @param {String}  searchCondition 请求的条件
     * @param {Element} 调用插件的元素的元素 DOM 元素
     */
    searchItem: function(searchCondition, element) {
      // 在满足输入条件的选项列表中搜索
      element.find('.combotree__item_last-level:containsIN("' + searchCondition + '")').addClass('search-result');

      // 显示符合搜索条件的变体的所有父代
      element.find('.search-result').each(function() {
        let allParentsItem = $(this).parents('.combotree__item');
        allParentsItem.each(function() {
          $(this).addClass('open');
        });

        let allParentsList = $(this).parents('.combotree__list');
        allParentsList.each(function() {
          $(this).addClass('open');
        });
      });
    },

    /**
     * 查找默认值
     * @param  {Array}  childrenData   有关列表选项的信息
     * @param  {String} defaultValueId 默认标识
     * @return
     */
    findDefaultItem: function(childrenData, defaultValueId) {
      for(let i = 0; i < childrenData.length; i++) {
        if(childrenData[i].id === defaultValueId) {
          return childrenData[i];
        }

        if(childrenData[i].children) {
          let res = methods.findDefaultItem(childrenData[i].children, defaultValueId);
          if(res) {
            return res;
          }
        }
      }

      return null;
    },

    /**
     * 显示一条消息，指出没有符合搜索条件的选项
     * @param {Element} 调用插件的元素的元素 DOM 元素
     */
    showListNoResultText: function(element) {
      if( element.find('.combotree__list-wrapper').children().hasClass('hidden') && !element.find('.combotree__search_no-result').length ) {
        $('<p class="combotree__search_no-result">Ничего не найдено</p>').appendTo( element.find('.combotree__list-wrapper') );
      }
    },

    /**
     * 隐藏不满足条件的变体，以及空的'父级'
     * @param {Element} element 要隐藏的 DOM 元素
     */
    hideListOptionsNotComplySearchConditions: function(element) {
      // 隐藏那些不符合条件的选项
      element.find('.combotree__item_last-level').each(function() {
        if( !$(this).hasClass('search-result') ) {
          $(this).addClass('hidden');
        }
      });

      // 隐藏空虚的父级
      element.find('.combotree__list').each(function() {
        methods.hideListEmptyParents( $(this) );
      });

      element.find('.combotree__item').not('.combotree__item_last-level').each(function() {
        methods.hideListEmptyParents( $(this) );
      });
    },

    /**
     * 隐藏空的非叶元素
     * @param {Element} element 要隐藏的 DOM 元素
     */
    hideListEmptyParents: function(element) {
      let hasShowListedItem = false;
      let lastLevel = element.find('.combotree__item_last-level');
      lastLevel.each(function() {
        if( $(this).hasClass('search-result') ) {
          hasShowListedItem = true;
          return false;
        }
      });

      if(!hasShowListedItem) {
        element.removeClass('open').addClass('hidden');
      }
    },

    /**
     * 重置搜索结果
     * @param {Element} element 调用插件的元素的元素 DOM 元素
     */
    resetSearchResult: function(element) {
      element.find('.combotree__list').removeClass('hidden');
      element.find('.combotree__item').removeClass('hidden');
      element.find('.combotree__item_last-level').removeClass('search-result');
      element.find('.combotree__search_no-result').remove();
    },

    /**
     * 输入字段值更改事件处理程序
     * @param {Object} event
     */
    onInputChange: function(event) {
      let $input = $(event.target);
      let $combotree = $(event.target).parents('.combotree');
      if ($input.val().length === 0) {
        // Условие поиска не введено
        if( $combotree.hasClass('open') ) {
          methods.resetSearchResult($combotree);
        }
      } else {
        // 输入搜索条件
        if( $combotree.hasClass('open') ) {
          // 显示的选项列表
          methods.resetSearchResult($combotree);
          methods.searchItem($input.val(), $combotree);
          methods.hideListOptionsNotComplySearchConditions($combotree);
          methods.showListNoResultText($combotree);
        } else {
          // 折叠的选项列表
          methods.showList($combotree);
          methods.searchItem($input.val(), $combotree);
          methods.hideListOptionsNotComplySearchConditions($combotree);
          methods.showListNoResultText($combotree);
        }
      }
    },

    /**
     * 下拉按钮的单击事件处理程序
     * @param {Object} event
     */
    onSelectClick: function(event) {
      event.preventDefault();
      let $combotree = $(event.target).parents('.combotree');
      if( $combotree.hasClass('open') ) {
        // Список вариантов развернут
        methods.hideList($combotree);
      } else {
        // Список вариантов свернут
        methods.showList($combotree);
      }
    },

    /**
     * 列表项单击事件处理程序
     * @param {Object} event
     */
    onItemClick: function(event) {
      event.stopPropagation();
      let $item = $(event.target);
      let $combotree = $item.parents('.combotree');
      if($item.hasClass('combotree__item')) {
        // 按下列表项
        if($item.hasClass('combotree__item_last-level')) {
          // 最后一层的元素被按下
          $combotree.find('.combotree__input').val($item.text());
          methods.hideList($combotree);
        } else {
          // 不是最后一级元素被点击
          event.stopPropagation();
          if($item.hasClass('open')) {
            // Элемент развернут
            $item.children().slideUp( $combotree.data('slideDur') );
            setTimeout(function() {
              $item.removeClass('open');
            }, 400);
          } else {
            // Элемент свернут
            $item.children().slideDown( $combotree.data('slideDur') );
            $item.addClass('open');
          }
        }
      }
    },

    /**
     * 屏幕上除组合树区域外任何位置的鼠标单击事件处理程序
     * @param {Object} event
     */
    onWindowMouseUp: function(event) {
      if( $('.combotree.open').has(event.target).length === 0) {
        methods.hideList( $('.combotree.open') );
      }
    }
  };

  $.fn.combotree = function(settings) {
    return methods.init.apply(this, arguments);
  };
})(jQuery);